package com.example.mydemoproject.config.aspect;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.PropertyFilter;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.MDC;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Objects;
import java.util.UUID;

/**
 *
 * @description: aspect for payment platform
 * @author: zmq
 * @create: 2021-12-15
 **/
@Slf4j
@Aspect
@Component
public class LogAspect {

    /**
     * 每次请求的ID
     */
    private static ThreadLocal<String> REQUEST_ID = new ThreadLocal<>();

    /**
     * 请求开始时间
     */
    private final ThreadLocal<Long> startTime = new ThreadLocal<>();

    /**
     * 不需要打印请求参数的方法(全类名.方法名)
     */
    private static final String[] EXCLUDE_LOG_METHOD = {};

    /**
     * 响应结果集最大打印长度
     */
    private static Integer MAX_RESPONSE_LENGTH = 9216;

    @Pointcut("execution(* com.example.mydemoproject.controller..*.* (..))")
    public void needPrint() {
        log.info("AppApiLogAspect.needPrint");
    }

    @Pointcut("needPrint()")
    public void logAspect() {
        log.info("AppApiLogAspect.logAspect");
    }

    /**
     * 功能描述: 请求参数日志打印
     *
     * @param joinPoint
     * @return void
     * @Author xusiyong
     * @Update zmq
     * @Date 2019/1/19 15:23
     * @UpdateDate 2021/12/15 13:37
     */
    @Before("logAspect() && @annotation(apiOperation)")
    public void doBefore(JoinPoint joinPoint, ApiOperation apiOperation) {
        //初始化请求参数
        String requestParamData = "";
        //初始化日志请求信息集
        StringBuffer logReqInfo = new StringBuffer();
        //初始化请求上下文
        HttpServletRequest request = getHttpServletRequest();

        String requestId = UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();
        LogAspect.REQUEST_ID.set(requestId);
        startTime.set(System.currentTimeMillis());

        String classType = joinPoint.getTarget().getClass().getName();
        Class<?> clazz = null;
        try {
            clazz = Class.forName(classType);
        } catch (ClassNotFoundException e) {
            log.warn("class not found", e);
        }

        String clazzName =  Objects.nonNull(clazz) ? clazz.getName() : null;
        String methodName = joinPoint.getSignature().getName();
        boolean isMultipart = ServletFileUpload.isMultipartContent(request);
        if (!isMultipart && isLogParam(clazzName, methodName)) {
            //获取请求参数
            requestParamData = getRequestParams(request,joinPoint);
        }

        String uri = request.getContextPath()+request.getServletPath() + request.getPathInfo();
        logReqInfo.append("请求开始【")
                .append("请求ID :").append(requestId)
                .append(",请求方式=" + request.getMethod())
                .append(",方法路径=" + uri)
                .append(",方法注释:").append(apiOperation.value())
                .append(",客户端地址=" + getClientIp(request))
                .append(",params=" + requestParamData)
                .append(",Referer=" + request.getHeader("Referer"))
                .append(",User-Agent=" + request.getHeader("User-Agent")).append("】");

        // 将requestId设置到日志中
        MDC.put("requestId", requestId);
        if (log.isInfoEnabled()) {
            log.info(logReqInfo.toString());
        }
    }

    /**
     * 功能描述: 响应参数日志打印
     *
     * @param result
     * @return
     * @Author xusiyong
     * @update zmq
     * @Date 2019/1/19 15:36
     * @Updatetime 2021/12/15 11:45
     */
    @AfterReturning(returning = "result", pointcut = "logAspect() && @annotation(apiOperation)")
    public void doAfterReturning(Object result, ApiOperation apiOperation) {

        String resultJson = JSON.toJSONString(result);
        if (resultJson.length() > MAX_RESPONSE_LENGTH) {
            resultJson = resultJson + "......";
        }

        StringBuffer logRespInfo = new StringBuffer();
        logRespInfo.append("请求结束【")
                .append("请求ID :").append(REQUEST_ID.get())
                .append(",方法注释:").append(apiOperation.value())
                .append(",响应结果 :").append(resultJson)
                .append(",响应耗时:" + (System.currentTimeMillis() - startTime.get())).append("ms").append("】");

        if (log.isInfoEnabled()) {
            log.info(logRespInfo.toString());
        }
        REQUEST_ID.remove();
        startTime.remove();
    }

    /**
     * 取得客户端的IP
     */
    public static String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }

    /**
     * 是否打印参数
     *
     * @param className
     * @param methodName
     * @return
     */
    public boolean isLogParam(String className, String methodName) {
        return !Arrays.asList(EXCLUDE_LOG_METHOD).contains(className + "." + methodName);
    }

    /**
     * 获取servlet上下文
     * @return HttpServletRequest
     * @author zmq
     * @since 2021/12/15 13:17
     */
    public static HttpServletRequest getHttpServletRequest() {
        return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
    }

    /**
     * 获取请求参数
     * @param request
     * @param joinPoint
     * @author zmq
     * @return String
     * @since 2021/12/15 11:30
     */
    private String getRequestParams(HttpServletRequest request, JoinPoint joinPoint){
        String httpMethod = request.getMethod();
        String returnParams = "";
        if ("POST".equals(httpMethod) || "PUT".equals(httpMethod) || "PATCH".equals(httpMethod)) {
            // 请求的方法参数值
            Object[] paramsArray = joinPoint.getArgs();
            Object[] arguments  = new Object[paramsArray.length];
            for (int i = 0; i < paramsArray.length; i++) {
                if (paramsArray[i] instanceof ServletRequest
                    || paramsArray[i] instanceof ServletResponse
                    || paramsArray[i] instanceof MultipartFile) {
                    continue;
                }
                arguments[i] = paramsArray[i];
            }
            PropertyFilter propertyFilter = new PropertyFilter() {
                @Override
                public boolean apply(Object o, String name, Object value) {
                    if(value!=null && value.toString().length()>500){
                        return false;
                    }
                    return true;
                }
            };
            returnParams = JSONObject.toJSONString(arguments, propertyFilter);
        } else {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            // 请求的方法参数值
            Object[] args = joinPoint.getArgs();
            // 请求的方法参数名称
            LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
            String[] paramNames = u.getParameterNames(method);
            if (args != null && paramNames != null) {
                for (int i = 0; i < args.length; i++) {
                    returnParams += "  " + paramNames[i] + ": " + args[i];
                }
            }
        }
        return returnParams;
    }

}
